﻿namespace Neovolve.Extensibility.ReSharper
{
    using System;
    using System.Linq;
    using JetBrains.DocumentModel;
    using JetBrains.ReSharper.Psi.Tree;

    /// <summary>
    /// The <see cref="NodeDescriptor"/>
    ///   class is used to provide node type detection and conversion logic.
    /// </summary>
    [CLSCompliant(false)]
    public abstract class NodeDescriptor
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="NodeDescriptor"/> class.
        /// </summary>
        /// <param name="node">
        /// The node managed by this converter.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="node"/> value is <c>null</c>.
        /// </exception>
        protected NodeDescriptor(ITreeNode node)
            : this(node, true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NodeDescriptor"/> class.
        /// </summary>
        /// <param name="node">
        /// The node managed by this converter.
        /// </param>
        /// <param name="analyizeNode">
        /// If set to <c>true</c> [analyize node].
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="node"/> value is <c>null</c>.
        /// </exception>
        protected NodeDescriptor(ITreeNode node, Boolean analyizeNode)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }

            Node = node;

            if (analyizeNode)
            {
                AnalyzeNode();
            }
        }

        /// <summary>
        /// Creates the alias type node.
        /// </summary>
        /// <returns>
        /// A <see cref="ITreeNode"/> value.
        /// </returns>
        public virtual ITreeNode CreateAliasTypeNode()
        {
            return CreateNodeForTypeName(TypeMapping.AliasTypeName);
        }

        /// <summary>
        /// Creates the CLR type node.
        /// </summary>
        /// <returns>
        /// A <see cref="ITreeNode"/> value.
        /// </returns>
        public virtual ITreeNode CreateClrTypeNode()
        {
            return CreateNodeForTypeName(TypeMapping.ClrTypeName);
        }

        /// <summary>
        /// Gets the document range.
        /// </summary>
        /// <returns>
        /// A <see cref="DocumentRange"/> value.
        /// </returns>
        protected internal virtual DocumentRange GetDocumentRange()
        {
            // The node may contain child nodes that are not the actual type that we are after, but the whole set must be replaced
            // For example, String.Empty requires that the descriptor manages the entire set of nodes of [[String][.][Empty]]
            // The document range should however only identity the type that we want to update
            String nodeText = Node.GetText();
            String typeText;

            if (IsClrType)
            {
                typeText = TypeMapping.ClrTypeName;
            }
            else
            {
                typeText = TypeMapping.AliasTypeName;
            }

            if (nodeText.Length == typeText.Length)
            {
                return Node.GetDocumentRange();
            }

            Int32 startShift = nodeText.IndexOf(typeText, StringComparison.Ordinal);
            Int32 endShift = nodeText.Length - startShift - typeText.Length;

            return Node.GetDocumentRange(startShift, endShift);
        }

        /// <summary>
        /// Analyzes the node.
        /// </summary>
        protected void AnalyzeNode()
        {
            if (IsSupportedNode == false)
            {
                return;
            }

            PopulateNodeInformation();
        }

        /// <summary>
        /// Creates the name of the node for type.
        /// </summary>
        /// <param name="typeName">
        /// Name of the type.
        /// </param>
        /// <returns>
        /// A <see cref="ITreeNode"/> value.
        /// </returns>
        protected abstract ITreeNode CreateNodeForTypeName(String typeName);

        /// <summary>
        /// Gets the name of the node type.
        /// </summary>
        /// <returns>
        /// A <see cref="String"/> instance.
        /// </returns>
        protected virtual String GetNodeTypeName()
        {
            return Node.GetText();
        }

        /// <summary>
        /// Populates the node information.
        /// </summary>
        protected virtual void PopulateNodeInformation()
        {
            String typeName = GetNodeTypeName();
            AliasTypeMapping aliasMapping = NodeAnalyser.AliasTypeMappings.Where(x => x.AliasTypeName == typeName).FirstOrDefault();

            if (aliasMapping != null)
            {
                TypeMapping = aliasMapping;
                IsClrType = false;
            }

            AliasTypeMapping clrMapping = NodeAnalyser.AliasTypeMappings.Where(x => x.ClrTypeName == typeName).FirstOrDefault();

            if (clrMapping != null)
            {
                TypeMapping = clrMapping;
                IsClrType = true;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the node is a CLR type.
        /// </summary>
        /// <value>
        /// <c>true</c> if the node is a CLR type; otherwise, <c>false</c>.
        /// </value>
        public virtual Boolean IsClrType
        {
            get;
            protected set;
        }

        /// <summary>
        /// Gets a value indicating whether the node is supported by this descriptor.
        /// </summary>
        /// <value>
        /// <c>true</c> if the node is supported by this descriptor; otherwise, <c>false</c>.
        /// </value>
        public abstract Boolean IsSupportedNode
        {
            get;
        }

        /// <summary>
        /// Gets a value indicating whether this instance is type mapping available.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is type mapping available; otherwise, <c>false</c>.
        /// </value>
        public Boolean IsValid
        {
            get
            {
                return TypeMapping != null && Node.IsValid();
            }
        }

        /// <summary>
        /// Gets or sets the node managed by this instance.
        /// </summary>
        /// <value>
        /// The node managed by this instance.
        /// </value>
        public ITreeNode Node
        {
            get;
            protected set;
        }

        /// <summary>
        /// Gets or sets the type mapping.
        /// </summary>
        /// <value>
        /// The type mapping.
        /// </value>
        public AliasTypeMapping TypeMapping
        {
            get;
            set;
        }
    }
}